#include "IntPerdoVisitor.h"
#include <iostream>
#include <sstream>
#include <ostream>
#include <stdarg.h>

#include "Char.h"
#include "Command.h"
#include "Id.h"
#include "Index.h"
#include "Number.h"
#include "StatementList.h"
#include "String.h"

#include "Add.h"
#include "Sub.h"
#include "Mul.h"
#include "Div.h"
#include "Mod.h"
#include "Neg.h"
#include "Equ.h"

#include "DoubleEqual.h"
#include "NotEqual.h"
#include "Less.h"
#include "LessEqual.h"
#include "Greater.h"
#include "GreaterEqual.h"
#include "And.h"
#include "Or.h"

#include "While.h"
#include "If.h"

#include "FunctionCall.h"
#include "FunctionDefinition.h"
#include "Return.h"
#include "ParamNode.h"
#include "ArgNode.h"

using namespace std;


IntPerdoVisitor::IntPerdoVisitor(SymbolTable * Sym, ostream * Out)
	:sym(Sym), out(Out), whileCount(0), ifCount(0)
{
	*out << "jump main" << endl;
}

void IntPerdoVisitor::visitDoubleEqual( DoubleEqual* DE )
{
	DE->getLeft()->accept(*this);
	DE->getRight()->accept(*this);

	string right = stk.top();
	stk.pop();
	string left = stk.top();
	stk.pop();
	string sTemp = sym->useNextScalar('n');

	writeout("if_equ ", left.c_str(), " ", right.c_str(), " ", sTemp.c_str(), "# Relational equality test");
	writeout("nop", "#");

	stk.push(sTemp);
}
void IntPerdoVisitor::visitGreater( Greater* GR )
{
	GR->getLeft()->accept(*this);
	GR->getRight()->accept(*this);

	string right = stk.top();
	stk.pop();
	string left = stk.top();
	stk.pop();

	string sTemp = sym->useNextScalar('n');

	writeout("if_gtr ", left.c_str(), " ", right.c_str(), " ", sTemp.c_str(), "# Greater than relational test");
	writeout("nop", "#");

	stk.push(sTemp);
}
void IntPerdoVisitor::visitLess( Less* LS )
{
	LS->getLeft()->accept(*this);
	LS->getRight()->accept(*this);

	string right = stk.top();
	stk.pop();
	string left = stk.top();
	stk.pop();

	string sTemp = sym->useNextScalar('n');

	writeout("if_less ", left.c_str(), " ", right.c_str(), " ", sTemp.c_str(), "# Less than relational test");
	writeout("nop", "#");

	stk.push(sTemp);
}
void IntPerdoVisitor::visitGreaterEqual( GreaterEqual* GE )
{
	GE->getLeft()->accept(*this);
	GE->getRight()->accept(*this);

	string right = stk.top();
	stk.pop();
	string left = stk.top();
	stk.pop();

	string sTemp = sym->useNextScalar('n');
	
	writeout("if_gtr_equ ", left.c_str(), " ", right.c_str(), " ", sTemp.c_str(), "# Greater than or equal relational test");
	writeout("nop", "#");

	stk.push(sTemp);
}
void IntPerdoVisitor::visitLessEqual( LessEqual* LE )
{
	LE->getLeft()->accept(*this);
	LE->getRight()->accept(*this);

	string right = stk.top();
	stk.pop();
	string left = stk.top();
	stk.pop();

	string sTemp = sym->useNextScalar('n');

	writeout("if_less_equ ", left.c_str(), " ", right.c_str(), " ", sTemp.c_str(), "# Less than or equal relational test");
	writeout("nop", "#");

	stk.push(sTemp);
}
void IntPerdoVisitor::visitNotEqual( NotEqual* NE )
{
	NE->getLeft()->accept(*this);
	NE->getRight()->accept(*this);

	string right = stk.top();
	stk.pop();
	string left = stk.top();
	stk.pop();

	string sTemp = sym->useNextScalar('n');

	writeout("if_n_equ ", left.c_str(), " ", right.c_str(), " ", sTemp.c_str(), "# Relational inequality test");
	writeout("nop", "#");

	stk.push(sTemp);
}
void IntPerdoVisitor::visitAnd( And* AND )
{
	AND->getLeft()->accept(*this);
	AND->getRight()->accept(*this);

	string right = stk.top();
	stk.pop();
	string left = stk.top();
	stk.pop();

	string sTemp = sym->useNextScalar('n');

	writeout("if_n_equ ", left.c_str(), " 0 ", sTemp.c_str(), "# Logical AND test");
	writeout("if_n_equ ", right.c_str(), " 0 ", sTemp.c_str(), "#");
	writeout("nop", "#");

	stk.push(sTemp);
}
void IntPerdoVisitor::visitOr( Or* OR )
{
	OR->getLeft()->accept(*this);
	OR->getRight()->accept(*this);

	string right = stk.top();
	stk.pop();
	string left = stk.top();
	stk.pop();

	string sTemp = sym->useNextScalar('n');
	string sTemp2 = sym->useNextScalar('n');

	writeout("if_n_equ ", left.c_str(), " 0 ", sTemp.c_str(), "# Logical OR test");
	writeout("if_equ ", sTemp.c_str(), " 0 ", sTemp2.c_str(), "#");
	writeout("if_n_equ ", right.c_str(), " 0 ", sTemp.c_str(), "#");
	writeout("nop", "#");

	stk.push(sTemp);
}
void IntPerdoVisitor::visitWhile( While* WL )
{
	incWhile();
	string whileX = getWhile();
	string done_whileX = getDoneWhile();

	string sTemp = sym->useNextScalar('n');

	*out << whileX << ":" << endl;
	WL->getCondition()->accept(*this);
	string condition = stk.top();
	//stk.pop();
	writeout("if_equ ", condition.c_str(), " 0 ", sTemp.c_str(), "# While condition check");
	writeout("jump ", done_whileX.c_str(), "#");
	WL->getBlock()->accept(*this);
	writeout("jump ", whileX.c_str(), "#");
	*out << done_whileX << ":" << endl;
}
void IntPerdoVisitor::visitIf( If* IF )
{
	incIf();
	string ifX = getIf();
	string elseX = getElse();
	string done_ifX = getDoneIf();

	string sTemp = sym->useNextScalar('n');

	*out << ifX << ":" << endl;
	IF->getCondition()->accept(*this);
	string condition = stk.top();
	stk.pop();
	writeout("if_equ ", condition.c_str(), " 0 ", sTemp.c_str(), "# If condition check");
	writeout("jump ", elseX.c_str(), "#");
	(*IF->getBlocks().begin())->accept(*this);
	writeout("jump ", done_ifX.c_str(), "#");
	writeout(elseX.c_str(), ":", "#");
	if( IF->getBlocks().size() > 1 )
	{
		(*(IF->getBlocks().end()-1))->accept(*this);
	}
	writeout(done_ifX.c_str(), ":", "#");
}

void IntPerdoVisitor::visitNumber( Number* NUM )
{
	string sTemp = sym->useNextScalar('n');

	stringstream ss;
	ss << NUM->getValue();
	writeout("val_copy ", ss.str().c_str(), " ", sTemp.c_str(), "# Literal numeric value");

	stk.push(sTemp);
}

void IntPerdoVisitor::visitIndex( Index* INDEX )
{
	INDEX->getIdentifier()->accept(*this);
	INDEX->getOffset()->accept(*this);

	if( INDEX->isLeft() )
	{	// Do nothing (on purpose)
	}
	else{	// Print out array_get_index stuff
		string right = stk.top();
		stk.pop();
		string left = stk.top();
		stk.pop();
		//int reg = sym->useNextRegister();
		// Get the int ID of left so we can look it up in the symbol table
		stringstream ss;
		char temp;
		ss << left;
		int reg;
		ss >> temp >> reg;
		string sTemp = sym->useNextScalar(sym->GetType(reg));
		writeout("array_get_index ", left.c_str(), " ", right.c_str(), " ", sTemp.c_str(), "# Get index of array");
		stk.push(sTemp);
	}
}

void IntPerdoVisitor::visitChar( Char* CHAR )
{
	string sTemp = sym->useNextScalar('c');
	writeout("val_copy ", CHAR->getValue(), " ", sTemp.c_str(), "# Literal character value");
	stk.push(sTemp);
}

void IntPerdoVisitor::visitString( String* STRING )
{
	string sTemp = sym->useNextArray('c');
	string value = STRING->getValue();
	// This is new to get the proper number of scalar variables for the PerdoVisitor
	string sTemp2 = sym->useNextScalar('n');
	stringstream ss;
	ss << value.size();
	writeout("val_copy ", ss.str().c_str(), " ", sTemp2.c_str(), "# Literal string");
	////////////////////////////////////////////////////////////////
	writeout("array_set_size ", sTemp.c_str(), " ", sTemp2.c_str(), "#");
	// This is new to get the proper number of scalar variables for the PerdoVisitor
	string sTemp3 = sym->useNextScalar('n');
	string sTemp4 = sym->useNextScalar('c');
	////////////////////////////////////////////////////////////////
	for( uint k = 0; k < value.size(); k++ )
	{
		*out << "val_copy " << k << " " << sTemp3 << endl;
		*out << "val_copy '" << value[k] << "' " << sTemp4 << endl;
		writeout("array_set_index ", sTemp.c_str(), " ", sTemp3.c_str(), " ", sTemp4.c_str(), "#");
	}
	stk.push(sTemp);
}

void IntPerdoVisitor::visitId( Id* ID )
{
	int id = ID->getId();
	string reg;
	stringstream ss;

	if( !sym->ValidId(id) )
	{
		cerr << "Something very bad happened in IntPerdoVisitor visitId()" << endl;
		exit(1);
	}

	ss << id;
	ss >> reg;
	reg = sym->GetVType(id) + reg;
	stk.push(reg);
}

void IntPerdoVisitor::visitStatementList( StatementList* SL )
{
	for( list<Node*>::const_iterator it = SL->getStatementList().begin(); it != SL->getStatementList().end(); it++ )
	{
		if( (*it)->amIaFunctionDefinition() )
			(*it)->accept(*this);
	}

	bool firsttime = true;
	for( list<Node*>::const_iterator it = SL->getStatementList().begin(); it != SL->getStatementList().end(); it++ )
	{
		if( !(*it)->amIaFunctionDefinition() )
		{
			if( SL->AmIRoot() && firsttime )
			{
				*out << "main:" << endl;
				firsttime = false;
			}
			(*it)->accept(*this);
		}
	}

	if( SL->AmIRoot() && firsttime )
		*out << "main:" << endl;
}

void IntPerdoVisitor::visitCommand( Command* COMMAND )
{
	for( list<Node*>::const_reverse_iterator rit = COMMAND->getExprList().rbegin(); rit != COMMAND->getExprList().rend(); rit++ )
		(*rit)->accept(*this);

	string n = COMMAND->getName();
	if( n == "print" )
	{

		for( list<Node*>::const_iterator it = COMMAND->getExprList().begin(); it != COMMAND->getExprList().end(); it++ )
		{
			// Get the int ID# of stk.top() so we can look it up in the symbol table
			stringstream ss;
			char temp;
			ss << stk.top();
			int reg;
			ss >> temp >> reg;

			string output;
			//check to see if it is a number or a character
			if (sym->GetType(reg) == 'n')
				output = "out_num ";
			else
				output = "out_char ";

			//check if it is an array or if it is a scalar
			if (sym->GetVType(reg) == 's')
			{
				*out << output << stk.top() << endl;
				//*out << "out_char '\\n'" << endl;
			}
			else
			{
				// Make counter that will also work at the index
				string sTemp = sym->useNextScalar('n');
				*out << "val_copy 0 " << sTemp << endl;

				// Make variable to store size of array in
				string sTemp2 = sym->useNextScalar('n');

				// Get the size of the array
				*out << "array_get_size " << stk.top() << " " << sTemp2 << endl;

				// Make while label
				incWhile();
				string whileX = getWhile();
				string done_whileX = getDoneWhile();
				*out << whileX << ":" << endl;

				// Make variable for condition answer
				string sTemp3 = sym->useNextScalar('n');

				// Make condition
				*out << "if_equ " << sTemp2 << " " << sTemp << " " << sTemp3 << endl;
				*out << "jump " << done_whileX << endl;

				// Write block for printing numbers
				// Increment counter
				string sTemp4 = sym->useNextScalar('n');
				*out << "array_get_index " << stk.top() << " " << sTemp << " " << sTemp4 << endl;
				*out << output << sTemp4 << endl;
				*out << "add 1 " << sTemp << " " << sTemp << endl;

				// Make end while label
				*out << "jump " << whileX << endl;
				*out << done_whileX << ":" << endl;
				//*out << "out_char '\\n'" << endl;
			}

			// Get rid of thing we just printed
			stk.pop();
		}
		*out << "out_char '\\n'" << endl;
	}
	else if( n == "time" )
	{
		string sTemp = sym->useNextScalar('n');
		*out << "time " << sTemp << endl;
		stk.push(sTemp);
	}
	else if( n == "random" )
	{
		string sTemp = sym->useNextScalar('n');
		*out << "random " << stk.top() << " " << sTemp << endl;
		stk.pop();
		stk.push(sTemp);
	}
	else if (n == "size")
	{
		string sTemp = sym->useNextScalar('n');
		string left = stk.top();
		stk.pop();
		*out << "array_get_size " << left << " " << sTemp << endl;
		stk.push(sTemp);
	}
	else if (n == "resize")
	{
		string array = stk.top();
		stk.pop();
		string newsize = stk.top();
		stk.pop();

		*out << "array_set_size " << array << " " << newsize << endl;
	}
	else if (n == "zeros")
	{
		// Make counter
		string sTemp = sym->useNextScalar('n');
		*out << "val_copy 0 " << sTemp << endl;

		// Make variable to store conditional answer in
		string sTemp2 = sym->useNextScalar('n');

		// Make temp array
		string sTemp3 = sym->useNextArray('n');
		*out << "array_set_size " << sTemp3 << " " << stk.top() << endl;

		// Make while label
		incWhile();
		string whileX = getWhile();
		string done_whileX = getDoneWhile();

		*out << whileX << ":" << endl;

		// Make condition
		*out << "if_equ " << stk.top() << " " << sTemp << " " << sTemp2 << endl;
		*out << "jump " << done_whileX << endl;

		// Write block for copying zeros
		// Increment counter
		*out << "array_set_index " << sTemp3 << " " << sTemp << " 0" << endl;
		*out << "add 1 " << sTemp << " " << sTemp << endl;

		// Make end while label
		*out << "jump " << whileX << endl;
		*out << done_whileX << ":" << endl;
		stk.pop();
		stk.push(sTemp3);
	}
	else if( n == "set_rotate" )
	{
		//void set_rotate(number angle);
		//rotate src
		*out << "rotate " << stk.top() << endl;
	}
	else if( n == "get_rotate" )
	{
		//number get_rotate();
		//rotate_stat dest
		string sTemp = sym->useNextScalar('n');
		*out << "rotate_stat " << sTemp << endl;
		stk.push(sTemp);
	}
	else if( n == "set_move" )
	{
		//void set_move(number distance);
		//move src
		*out << "move " << stk.top() << endl;
	}
	else if( n == "get_move" )
	{
		//number get_move();
		//move_stat dest
		string sTemp = sym->useNextScalar('n');
		*out << "move_stat " << sTemp << endl;
		stk.push(sTemp);
	}
	else if( n == "set_shields" )
	{
		//void set_shields(number energy);
		//shields src
		*out << "shields " << stk.top() << endl;
	}
	else if( n == "get_shields" )
	{
		//number get_shields();
		//shield_stat dest
		string sTemp = sym->useNextScalar('n');
		*out << "shield_stat " << sTemp << endl;
		stk.push(sTemp);
	}
	else if( n == "fire" )
	{
		//void fire(number energy);
		//fire src
		*out << "fire " << stk.top() << endl;
	}
	else if( n == "get_energy" )
	{
		//number get_energy();
		//energy_stat dest
		string sTemp = sym->useNextScalar('n');
		*out << "energy_stat " << sTemp << endl;
		stk.push(sTemp);
	}
	else if( n == "scan_distance" )
	{
		//number scan_distance();
		//scan distance ID angle
		string tempDist = sym->useNextScalar('n');
		string tempID = sym->useNextScalar('n');
		string tempAngle = sym->useNextScalar('n');
		*out << "scan " << tempDist << " " << tempID << " " << tempAngle << endl;
		stk.push(tempDist);
	}
	else if( n == "scan_angle" )
	{
		//number scan_angle();
		//scan distance ID angle
		string tempDist = sym->useNextScalar('n');
		string tempID = sym->useNextScalar('n');
		string tempAngle = sym->useNextScalar('n');
		*out << "scan " << tempDist << " " << tempID << " " << tempAngle << endl;
		stk.push(tempAngle);
	}
	else if( n == "scan_id" )
	{
		//number scan_id();
		//scan distance ID angle
		string tempDist = sym->useNextScalar('n');
		string tempID = sym->useNextScalar('n');
		string tempAngle = sym->useNextScalar('n');
		*out << "scan " << tempDist << " " << tempID << " " << tempAngle << endl;
		stk.push(tempID);
	}
}

void IntPerdoVisitor::visitAdd( Add* ADD )
{
	ADD->getLeft()->accept(*this);
	ADD->getRight()->accept(*this);

	string right = stk.top();
	stk.pop();
	string left = stk.top();
	stk.pop();

	*out << "add " << left << " " << right << " ";
	string sTemp = sym->useNextScalar('n');
	*out << sTemp;
	*out << endl;

	stk.push(sTemp);
}

void IntPerdoVisitor::visitSub( Sub* SUB )
{
	SUB->getLeft()->accept(*this);
	SUB->getRight()->accept(*this);

	string right = stk.top();
	stk.pop();
	string left = stk.top();
	stk.pop();

	*out << "sub " << left << " " << right << " ";
	string sTemp = sym->useNextScalar('n');
	*out << sTemp;
	*out << endl;

	stk.push(sTemp);
}

void IntPerdoVisitor::visitMul( Mul* MUL )
{
	MUL->getLeft()->accept(*this);
	MUL->getRight()->accept(*this);

	string right = stk.top();
	stk.pop();
	string left = stk.top();
	stk.pop();

	*out << "mult " << left << " " << right << " ";
	string sTemp = sym->useNextScalar('n');
	*out << sTemp;
	*out << endl;

	stk.push(sTemp);
}

void IntPerdoVisitor::visitDiv( Div* DIV )
{
	DIV->getLeft()->accept(*this);
	DIV->getRight()->accept(*this);

	string right = stk.top();
	stk.pop();
	string left = stk.top();
	stk.pop();

	*out << "div " << left << " " << right << " ";
	string sTemp = sym->useNextScalar('n');
	*out << sTemp;
	*out << endl;

	stk.push(sTemp);
}

void IntPerdoVisitor::visitMod( Mod* MOD )
{
	MOD->getLeft()->accept(*this);
	MOD->getRight()->accept(*this);

	string right = stk.top();
	stk.pop();
	string left = stk.top();
	stk.pop();

	*out << "mod " << left << " " << right << " ";
	string sTemp = sym->useNextScalar('n');
	*out << sTemp;
	*out << endl;

	stk.push(sTemp);
}

void IntPerdoVisitor::visitEqu( Equ* EQU )
{
	//parent_is_equ = true;
	
	EQU->getLeft()->accept(*this);
	//parent_is_equ = false;
	EQU->getRight()->accept(*this);
	if(EQU->isLeft())
	{
		string source = stk.top();
		stk.pop();
		string offset = stk.top();
		stk.pop();
		string identifier = stk.top();
		stk.pop();
		*out << "array_set_index " << identifier << " " << offset << " " << source << endl;
	}
	else
	{
		if( sym->GetVType(dynamic_cast<Id*>(EQU->getLeft())->getId()) == 's' )
			*out << "val_copy ";
		else
			*out << "array_copy ";

		*out << stk.top();
		stk.pop();
		*out << " ";
		*out << stk.top();
		stk.pop();
		*out << endl;
	}

}

void IntPerdoVisitor::visitNeg( Neg* NEG )
{
	NEG->getChild()->accept(*this);
	
	*out << "mult ";
	*out << stk.top();
	stk.pop();
	*out << " -1 ";
	string sTemp = sym->useNextScalar('n');
	*out << sTemp;
	*out << endl;

	stk.push(sTemp);
}

void IntPerdoVisitor::visitFunctionCall( FunctionCall * FC )
{
	// Call accept on argument child
	FC->getArgNode()->accept(*this);

	// Drop a label
	stringstream ss;
	ss << sym->useNextBlock();
	string function_return_lbl = "func_return_" + string(sym->getLabel(FC->getId())) + ss.str();

	// Print jump to function body
	*out << "val_copy " << function_return_lbl << " " << sym->GetReturn(FC->getId()) << endl;
	*out << "jump " << sym->getLabel(FC->getId()) << endl;
	*out << function_return_lbl << ":" << endl;

	// Push the return variable name (temporary that is allocated specifically for this function call)
	// onto the stack
	stringstream ss1;
	ss1 << sym->GetVType(FC->getId());
	ss1 << FC->getId();
	stk.push(ss1.str());
}

void IntPerdoVisitor::visitFunctionDefinition( FunctionDefinition * FD )
{
	// Visit ParamNode child
	FD->getParamNode()->accept(*this);

	// Print the function label
	*out << sym->getLabel(FD->getId()) << ":" << endl;

	// Visit code block child
	FD->getCodeBlock()->accept(*this);
}

void IntPerdoVisitor::visitReturn( Return * R )
{
	// Visit expression child
	R->getExpr()->accept(*this);

	// Copy the passed up value to the allocated return variable
	string returnExpr = stk.top();
	stk.pop();

	int f = R->getFunc();

	if( returnExpr[0] == 's' )
		*out << "val_copy ";
	else
		*out << "array_copy ";

	*out << returnExpr << " " << sym->GetVType(f) << f << endl;

	// Print jump return label
	*out << "jump " << sym->GetReturn(R->getFunc()) << endl;
}

void IntPerdoVisitor::visitParamNode( ParamNode * PN )
{
	// Call accept on children ??? -- really need to? -- NO!! will break things?
	//PN->getParamList()->accept(this);
}

void IntPerdoVisitor::visitArgNode( ArgNode * AN )
{
	// Call accept on all children.  For each one, copy the value passed up into
	// the allocated temporary (parameter) variable (recorded in the symbol
	// table)
	list<int> pl = sym->getParameters(AN->getFunc());
	list<Node*> l = AN->getArgList();
	list<int>::iterator itPL = pl.begin();
	list<Node*>::iterator it = l.begin();
	for( ; it != l.end(); itPL++, it++ )
	{
		(*it)->accept(*this);
		string PassedVar = stk.top();
		stk.pop();
		if( PassedVar[0] == 's' )
			*out << "val_copy ";
		else
			*out << "array_copy ";

		*out << PassedVar << " " << sym->GetVType(*itPL) << *itPL << endl;
	}
}

void IntPerdoVisitor::incWhile()
{ whileCount++; }

void IntPerdoVisitor::incIf()
{ ifCount++; }

std::string IntPerdoVisitor::getWhile()
{
	stringstream ss;
	ss << whileCount;
	return string("while") + ss.str();
}

std::string IntPerdoVisitor::getDoneWhile()
{
	stringstream ss;
	ss << whileCount;
	return string("done_while") + ss.str();
}

std::string IntPerdoVisitor::getIf()
{
	stringstream ss;
	ss << ifCount;
	return string("if") + ss.str();
}

std::string IntPerdoVisitor::getElse()
{
	stringstream ss;
	ss << ifCount;
	return string("else") + ss.str();
}

std::string IntPerdoVisitor::getDoneIf()
{
	stringstream ss;
	ss << ifCount;
	return string("done_if") + ss.str();
}

void IntPerdoVisitor::writeout(const char * first, ...)
{
	int spacing = 0;
	int width = 40;

	if( out == NULL ) return;

	const char * temp = first;
	stringstream ss;

	va_list vl;
	va_start(vl, first);

	// Set the left spacing
	out->width(spacing);
	*out << "";

	// Set the instruction spacing
	out->width(width);
	// Get all the args that are not the comment
	while( temp[0] != '#' )
	{
		ss << temp;
		temp = va_arg(vl, const char *);
	}
	// Output the instruction
	*out << left << ss.str();

	// Output the comment
	*out << endl;
	//*out << temp << endl;

	va_end(vl);
}

